package love.xiaohh.minio.services.impl;

import com.google.common.io.ByteStreams;
import io.minio.GetObjectResponse;
import io.minio.StatObjectResponse;
import love.xiaohh.minio.constants.MinioConstant;
import love.xiaohh.minio.entities.FileStore;
import love.xiaohh.minio.entities.dto.Base64ImageUploadDTO;
import love.xiaohh.minio.mapper.FileStoreMapper;
import love.xiaohh.minio.services.FileStoreService;
import love.xiaohh.minio.services.MinioService;
import love.xiaohh.minio.utils.file.FileUtils;
import love.xiaohh.minio.utils.sign.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 文件存取的服务层实现
 * </p>
 *
 * @author XiaoHH
 * @version 1.0
 * @date 2021-11-21 星期日 9:58:44
 * @file FileStoreServiceImpl.java
 */
@Service
public class FileStoreServiceImpl implements FileStoreService {

    @Autowired
    private MinioService minioService;

    @Autowired
    private FileStoreMapper fileStoreMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 文件目录的分隔符
     */
    private static final String FILE_SEPARATOR = "/";

    /**
     * 文件在redis当中存储的前缀
     */
    private static final String REDIS_KEY_PREFIX = "CostFile:uuid:";

    /**
     * 上传普通文件
     *
     * @param file 普通文件
     * @return 文件对象
     */
    @Override
    public FileStore upload(MultipartFile file) throws SQLException {
        // 文件的uuid
        String uuid = UUID.randomUUID().toString();
        // 文件名
        String originalFilename = file.getOriginalFilename();
        // 存储桶
        String bucket = FileUtils.isImage(file.getContentType()) ? MinioConstant.IMAGE_BUCKET : MinioConstant.FILE_BUCKET;
        // 生成对象名称
        LocalDate localDate = LocalDate.now();
        String objectName = localDate.getYear() + FILE_SEPARATOR + localDate.getMonthValue() + FILE_SEPARATOR
                + localDate.getDayOfMonth() + FILE_SEPARATOR + uuid + FileUtils.getExtension(originalFilename);

        // 文件存储对象
        FileStore fileStore = new FileStore(uuid, bucket, objectName, originalFilename);
        int rows = this.fileStoreMapper.insertFileStore(fileStore);
        if (rows < 1) throw new SQLException("存储文件存储对象时失败");

        // 将文件存入文件系统
        this.minioService.putObject(bucket, objectName, file);
        // 将文件属性存入缓存
        this.storeFile(fileStore);
        return fileStore;
    }

    /**
     * 上传base64格式的图片
     *
     * @param base64ImageUpload base64格式图片的数据传输对象
     * @return 文件对象
     */
    @Override
    public FileStore uploadBase64(Base64ImageUploadDTO base64ImageUpload) throws IOException {
        // 读取文件内容
        String base64 = base64ImageUpload.getBase64();
        // 文件类型
        String contentType = base64.substring(5, base64.indexOf(";base64,"));
        // 提取base64字符串并转换为字节数组
        base64 = base64.substring(base64.indexOf(",") + 1);
        byte[] decode = Base64.decode(base64);
        // 如果为空则代表转换错误
        if (null == decode || decode.length == 0) {
            throw new IOException("传入base64格式错误");
        }
        // 文件唯一标识
        final String uuid = UUID.randomUUID().toString();
        // 生成文件名
        String fileName = uuid + FileUtils.getExtensionByContentType(contentType);
        // 生成对象名称
        LocalDate localDate = LocalDate.now();
        final String objectName = localDate.getYear() + FILE_SEPARATOR + localDate.getMonthValue() + FILE_SEPARATOR
                + localDate.getDayOfMonth() + FILE_SEPARATOR + fileName;
        // 生成文件属性存储实体
        final FileStore fileStore = new FileStore(uuid, MinioConstant.IMAGE_BUCKET, objectName, fileName);
        // 将文件插入数据库
        int rows = this.fileStoreMapper.insertFileStore(fileStore);
        if (rows < 1) throw new IOException("哎呀，累趴了，请稍后再试");

        // 将字节数组转换为输入流并上传文件系统
        InputStream inputStream = new ByteArrayInputStream(decode);
        this.minioService.putObject(MinioConstant.IMAGE_BUCKET, objectName, contentType, inputStream);

        // 将文件对象存入缓存
        storeFile(fileStore);

        return fileStore;
    }

    /**
     * 下载或者在线预览文件
     *
     * @param download 是否下载；0=在线预览,1=下载
     * @param uuid     文件的uuid
     */
    @Override
    public void download(Integer download, String uuid, HttpServletResponse response) throws IOException {
        // 文件信息
        FileStore fileStore = this.getFileStore(uuid);
        if (null == fileStore) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            throw new IOException("您的文件离家出走了");
        }
        // 在minio中获取文件信息
        final GetObjectResponse minioFileSteam = this.minioService.getObject(fileStore.getBucket(), fileStore.getObjectName());
        final StatObjectResponse statObject = this.minioService.getStatObject(fileStore.getBucket(), fileStore.getObjectName());
        if (null == minioFileSteam || null == statObject) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            throw new IOException("您的文件离家出走了");
        }

        // 设置响应属性
        response.setContentType(statObject.contentType());
        // 设置响应文件的大小
        response.setContentLength((int) statObject.size());

        // 如果是下载，那么添加下载的文件名
        if (download == 1)
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileStore.getFileName(), "UTF-8"));

        // 输出文件
        ServletOutputStream outputStream = response.getOutputStream();
        ByteStreams.copy(minioFileSteam, outputStream);
        outputStream.flush();
        outputStream.close();
        response.flushBuffer();
        minioFileSteam.close();
    }

    /**
     * 删除一个文件
     *
     * @param uuid 文件的uuid
     */
    @Override
    public void removeFileByUuid(String uuid) {
        // 文件信息
        FileStore fileStore = this.getFileStore(uuid);
        if (null == fileStore) {
            return;
        }

        // 在文件系统中删除文件
        this.minioService.removeObject(fileStore.getBucket(), fileStore.getObjectName());

        // 从缓存中删除文件信息
        this.removeFileOnCache(uuid);
        // 从数据库中删除文件信息
        this.fileStoreMapper.deleteFileStore(uuid);
    }

    /**
     * 将文件对象存入缓存中
     *
     * @param fileStore 文件属性存储对象
     */
    private void storeFile(FileStore fileStore) {
        this.redisTemplate.opsForValue().set(REDIS_KEY_PREFIX + fileStore.getUuid(), fileStore, 1L, TimeUnit.HOURS);
    }

    /**
     * 在缓存中删除对应uuid文件的缓存
     *
     * @param uuid 文件的uuid
     */
    private void removeFileOnCache(String uuid) {
        this.redisTemplate.delete(REDIS_KEY_PREFIX + uuid);
    }

    /**
     * 根据文件的uuid找出文件的属性存储实体
     *
     * @param uuid 属性存储实体的uuid
     * @return 属性存储实体
     */
    private FileStore getFileStore(String uuid) {
        Object fileObject = this.redisTemplate.opsForValue().get(REDIS_KEY_PREFIX + uuid);
        if (null != fileObject) {
            return (FileStore) fileObject;
        } else {
            FileStore fileStore = this.fileStoreMapper.selectFileStoreByUuid(uuid);
            this.storeFile(fileStore);
            return fileStore;
        }
    }
}
